home *** CD-ROM | disk | FTP | other *** search
/ C/C++ Users Group Library 1997 August / Walnut Creek CDROM.7z / VOL_400 / 466_01 / SRC / OUTPUT.CPP < prev    next >
Encoding:
C/C++ Source or Header  |  1996-12-20  |  54.8 KB  |  2,067 lines

  1. #include <afx.h>
  2. #include <afxtempl.h>
  3. #include "parse.h"
  4. #include "topiclog.h"
  5. #include "docexpr.h"
  6. #include "fmtspec.h"
  7. #include "cmdargs.h"
  8. #include "output.h"
  9. #include "errmsg.h"
  10.  
  11. char szNewline[] = "\r\n";
  12. char szAutoduckEscape[] = "<>/.@\\";
  13.  
  14. //@doc EXTERNAL
  15.  
  16. /*
  17. @struct This structure defines an entry in a stack maintained
  18. by the function that outputs a formatted topic. The stack keeps track of
  19. the tag and tag-format information occurring at various output levels
  20. within the topic.
  21. */
  22.  
  23. struct FORMATSTACK {
  24.     CFmtPara     *pfmtPara;        // @field Paragraph formatting information
  25.     CTag         *ptagPara;        // @field Extracted tag text for paragraph
  26.     FORMATSTACK *next;            // @field Pointer to the next entry in stack
  27. };
  28. typedef struct FORMATSTACK *PFORMATSTACK;
  29.  
  30.  
  31.  
  32. /***************************************************************************************/
  33.  
  34. PFORMATSTACK Push(PFORMATSTACK pStack, CFmtPara *pfmtPara, CTag *ptag);
  35. PFORMATSTACK Pop(PFORMATSTACK pStack);
  36. PFORMATSTACK OutputParagraph(CTag *ptag, CFmtPara *pfmtPara, CCurTopicInfo &cur, PFORMATSTACK pStack);
  37. void EmptyStack(PFORMATSTACK pStack, CCurTopicInfo &cur);
  38. int ExtractTextTag(const char *&sz, CTag &tagText, int &nSrcLineOffset);
  39.  
  40. void OutputFormatChar(CCurTopicInfo &cur, const char *&sz, CFmtBase *pfmt);
  41. void OutputField(CCurTopicInfo &cur, CTag *ptag, int  nField, char  cFieldRef);
  42. void OutputFieldChar(CCurTopicInfo &cur, char ch, TokenContexts = ctxBody);
  43. void OutputTextTag(CCurTopicInfo &cur, CTag *ptag, const char *&sz, int &nSrcLineOffset);
  44. int OutputBuildInfo(CCurTopicInfo &cur, const char *&sz, CTag *ptag, CFmtBase *pfmt);
  45. void OutputDiagram(CCurTopicInfo &cur, CFmtBase *pfmt, const char *&sz, CTag *ptag);
  46. int OutputIndex(CCurTopicInfo &cur, CFmtBase *pfmt, const char *&sz, CTag *ptag);
  47. int OutputConstant(CCurTopicInfo &cur, const char *&sz);
  48.  
  49. void MakeContextString(char *szContext, const char *szText);
  50.  
  51. /***************************************************************************************/
  52.  
  53. CCurTopicInfo::CCurTopicInfo(
  54.     CTopic *pTopic, 
  55.     CFile &fileOutput, 
  56.     FormatInfo &fmt, 
  57.     RUNOPTIONS &run, 
  58.     CTopicLog &log) : 
  59.         m_pTopic(pTopic), m_fileOutput(fileOutput), m_fmt(fmt), m_run(run), m_log(log)
  60. {
  61. }
  62.  
  63.  
  64.  
  65. /***************************************************************************************/
  66.  
  67.  
  68. inline void PrintFmtError(
  69.     CCurTopicInfo &cur, 
  70.     CFmtBase *pfmt, 
  71.     int err)
  72. {
  73.     PrintError(cur.m_fmt.GetSrcFn(pfmt->GetSrcFn()), pfmt->GetSrcLineNum(), err);
  74. }
  75.  
  76.  
  77. inline void PrintTagError(
  78.     CCurTopicInfo &cur,
  79.     CTag *ptag,
  80.     int nSrcLineOffset,
  81.     int err)
  82. {
  83.     PrintError(cur.m_log.GetSrcFn(cur.m_pTopic->GetSrcFn()), 
  84.                     ptag->lSrcLineNum + nSrcLineOffset, 
  85.                     err);
  86. }
  87.  
  88.  
  89. /*
  90. @func This function writes an Autoduck topic to the final output file.
  91. */
  92.  
  93. void OutputTopic(
  94.     CTopic      *pTopic,
  95.     RUNOPTIONS     &run,
  96.     FormatInfo     &fmt,
  97.     CFile          &fileOutput,
  98.     CFile       &fileTemp,
  99.     CTopicLog      &log)
  100. {
  101.     int nSrcLineOffset = 0;
  102.  
  103.     PFORMATSTACK pStack = NULL;
  104.     CCurTopicInfo cur(pTopic, fileOutput, fmt, run, log);
  105.     
  106.     CFmtSrchTopic srchTopic;
  107.     CFmtSrchPara  srchPara;
  108.     CFmtTopic *pfmtTopic;
  109.     CFmtPara  *pfmtPara;
  110.  
  111.     int nRet = pTopic->Read(fileTemp);
  112.     if(nRet)
  113.     {
  114.         PrintError(run.sTempFile, NO_LINE, nRet);
  115.         return;
  116.     }
  117.  
  118.     CTagList &listTags = pTopic->GetTags();
  119.     CTag *ptagHead = listTags.GetHead();
  120.     CTag *ptag;
  121.  
  122.     int nCurLevel;
  123.     
  124.     // Get the topic formatting structure.
  125.  
  126.     srchTopic.m_sName = ptagHead->sTag;
  127.     srchTopic.m_pLog  = &log;
  128.     srchTopic.m_ptagTag = ptagHead;
  129.     srchTopic.m_plistTags = &listTags;
  130.  
  131.     pfmtTopic = (CFmtTopic *)fmt.topic.Get(&srchTopic);
  132.     if(NULL == pfmtTopic)
  133.     {
  134.         PrintTagError(cur, ptagHead, 0, warnNoTopicFormat);
  135.         return;
  136.     }
  137.     
  138.     // Output topic pre-text.
  139.  
  140.     OutputFormatString(cur, pfmtTopic, CFmtListTopic::tagPre, ptagHead);
  141.  
  142.     // Initialize tag list as "not output yet"
  143.  
  144.     POSITION posTag = listTags.GetHeadPosition();
  145.     while( posTag )
  146.         listTags.GetNext( posTag )->nState.WasOutput = FALSE;
  147.  
  148.     // Output topics named in order list.
  149.  
  150.     CString  *psGroup;
  151.     POSITION posGroup = pfmtTopic->m_listParaTags.GetHeadPosition();
  152.     while( posGroup != NULL )
  153.     {
  154.         psGroup = pfmtTopic->m_listParaTags.GetNext( posGroup );
  155.         ASSERT(psGroup);
  156.  
  157.         nCurLevel = MAXLEVEL;
  158.         
  159.         posTag = listTags.GetHeadPosition();
  160.         ptagHead = listTags.GetNext( posTag );
  161.         while( posTag )
  162.         {
  163.             ptag = listTags.GetNext( posTag );
  164.  
  165.             // Skip tag if already dealt with.
  166.             
  167.             if(ptag->nState.WasOutput)
  168.                 continue;
  169.                 
  170.                // Get the formatting struct for the tag.
  171.  
  172.             srchPara.m_sName     = ptag->sTag;
  173.             srchPara.m_pLog      = &log;
  174.             srchPara.m_ptagTag     = ptag;
  175.             srchPara.m_plistTags = &listTags;
  176.  
  177.             pfmtPara = (CFmtPara *)fmt.paragraph.Get(&srchPara);
  178.             if(NULL == pfmtPara)
  179.             {
  180.                 // Error: could not find format string for this tag
  181.                     
  182.                 ptag->nState.WasOutput = TRUE;
  183.                                         
  184.                 PrintTagError(cur, ptag, 0, warnNoParaFormat);
  185.             
  186.                 continue;
  187.             }
  188.             
  189.             // Determine whether to output tag. We output tag if the tag name
  190.             // matches the one we're looking for, OR if tag is subordinate to
  191.             // a tag previously output.
  192.             
  193.             // Does the tag name match? If so, record the level of the current
  194.             // tag; we will output this tag plus all following tags that are
  195.             // subordinate to the current one.
  196.             
  197.             if(0 == _stricmp(ptag->sTag, *psGroup))
  198.             {
  199.                 nCurLevel = pfmtPara->GetLevel();
  200.  
  201.                    pStack = OutputParagraph(ptag, pfmtPara, cur, pStack);
  202.             }
  203.             
  204.             // We might still output the tag if the tag is one of a group of
  205.             // subordinate-level tags immediately following a previously
  206.             // matched tag.
  207.             
  208.             else if(pfmtPara->GetLevel() > nCurLevel)
  209.             {
  210.                    pStack = OutputParagraph(ptag, pfmtPara, cur, pStack);
  211.             }
  212.             
  213.             // Set the level to the max value, to indicate we have found the
  214.             // last of the subordinate tags.
  215.             
  216.             else
  217.             {
  218.                 nCurLevel = MAXLEVEL;            
  219.             }
  220.         }
  221.     }
  222.  
  223.     // Output remaining tags (those not named within ordering list)
  224.  
  225.     posTag = listTags.GetHeadPosition();
  226.     ptagHead = listTags.GetNext( posTag );
  227.     while( posTag )
  228.     {
  229.         ptag = listTags.GetNext( posTag );
  230.  
  231.         if(ptag->nState.WasOutput)
  232.             continue;
  233.  
  234.         srchPara.m_sName     = ptag->sTag;
  235.         srchPara.m_pLog      = &log;
  236.         srchPara.m_ptagTag     = ptag;
  237.         srchPara.m_plistTags = &listTags;
  238.  
  239.         pfmtPara = (CFmtPara *)fmt.paragraph.Get(&srchPara);
  240.         if(NULL == pfmtPara)
  241.         {
  242.             // Error: could not find format string for this tag
  243.                 
  244.             PrintTagError(cur, ptag, 0, warnNoParaFormat);
  245.         
  246.             continue;
  247.         }
  248.         
  249.         pStack = OutputParagraph(ptag, pfmtPara, cur, pStack);
  250.     }
  251.     
  252.     // Output any residual tag post-texts and empty the stack.
  253.     
  254.     EmptyStack(pStack, cur);
  255.  
  256.     // Output topic post-text.
  257.     
  258.     OutputFormatString(cur, pfmtTopic, CFmtListTopic::tagPost, ptagHead);
  259.  
  260.     pTopic->FreeTagText();
  261. }
  262.  
  263.  
  264. /*
  265. @func This function pushes a format/tag entry onto the formatting stack.
  266.  
  267. @parm PFORMATSTACK | pStack | The stack on which to push the entries.
  268.  
  269. @parm PFORMATENTRY | ptagfmt | The format entry to push.
  270.  
  271. @parm PTAG | ptag | The tag information to push.
  272.  
  273. @rdesc Returns the head of the new stack.
  274. */
  275. PFORMATSTACK Push(
  276.     PFORMATSTACK pStack,    //@parm The stack
  277.     CFmtPara *pfmtPara,     //@parm The paragraph format.
  278.     CTag *ptag)             //@parm The extracted tag.
  279. {
  280.     PFORMATSTACK pNew = new FORMATSTACK;
  281.     
  282.     pNew->next = pStack;
  283.     pNew->pfmtPara = pfmtPara;
  284.     pNew->ptagPara = ptag;
  285.     
  286.     return pNew;
  287. }
  288.  
  289.  
  290. /*
  291. @func This function pops a format/tag entry
  292. off the formatting stack and frees associated memory.
  293.  
  294. @rdesc Returns the head of the new stack.
  295. */
  296. PFORMATSTACK Pop(
  297.     PFORMATSTACK pStack)    //@parm The stack.
  298. {
  299.     PFORMATSTACK ret = pStack->next;
  300.     
  301.     delete pStack;
  302.     
  303.     return ret;
  304. }
  305.  
  306.  
  307. /*
  308. @func void | EmptyStack | This function clears all entries from the
  309. stack, freeing memory in the process. It also outputs post-text for
  310. each stack entry.
  311.  
  312. @parm PFORMATSTACK | pStack | The stack to empty.
  313.  
  314. @parm PCURTOPICINFO | pcur | Specifies a pointer to a <t CURTOPICINFO>
  315. structure containing information about the output temp file, 
  316. formatting information, etc.
  317.  
  318. */
  319. void EmptyStack(PFORMATSTACK pStack, CCurTopicInfo &cur)
  320. {
  321.     while(pStack)
  322.     {
  323.         OutputFormatString(cur, pStack->pfmtPara, CFmtListPara::tagPost, pStack->ptagPara);
  324.         
  325.         pStack = Pop(pStack);
  326.     }
  327. }
  328.  
  329. /*
  330.  
  331. @func This function adjusts the contents of the formatting stack. 
  332. It performs all the work required for handling levels within entry tags.
  333.  
  334. @parm PFORMATSTACK | pStack | The stack to adjust.
  335.  
  336. @parm PFORMATENTRY | ptagfmt | Specifies a pointer to a <t FORMATENTRY>
  337. structure containing the formatting information for the currently 
  338. processed tag.
  339.  
  340. @parm PTAG | ptag | Specifies a pointer to a <t TAG> structure 
  341. containing the field values for the currently processed tag.
  342.  
  343. @parm PCURTOPICINFO | pcur | Specifies a pointer to a <t CURTOPICINFO>
  344. structure containing information about the output temp file, 
  345. formatting information, etc.
  346.  
  347. @rdesc Returns a pointer to a <t FORMATSTACK> structure containing
  348. the head of the new stack.
  349.  
  350. @comm This function performs one of a series of actions depending on how
  351. the current tag (represented by <p ptagfmt> and <p ptag>) compares to the
  352. tag on the head of the stack.
  353.  
  354. If the stack is empty, we output the pre-text for the current tag and push
  355. the current tag onto the stack.
  356.  
  357. If the tag names are the same, we don't change configuration of the stack
  358. or output any pre/post text; we just substitute the field information of 
  359. the current tag for the field information stored on the stack (for the 
  360. previous tag).
  361.  
  362. If the tag names are different, and the current tag has a higher level
  363. value than the stack value, we are descending to a lower tag level. We
  364. output the pre-text for the current tag and push the current tag onto
  365. the stack.
  366.  
  367. If the tag names are different, and the current tag has the same level
  368. as the stack value, we output the post-text for the stack value; output
  369. the pre-text for the current value; and substitute the current tag
  370. values for the values stored on the head of the stack.
  371.  
  372. If the tag names are different, and the current tag has a lower level 
  373. value, we need to pop tag values off the stack. We output the post-text
  374. for the stack value, pop the stack, and recurse. The next function
  375. call will need to look at all the preceding situations and proceed
  376. accordingly.
  377. */
  378. PFORMATSTACK AdjustStack(
  379.     PFORMATSTACK pStack, 
  380.     CFmtPara *pfmtPara, 
  381.     CTag *ptag,
  382.     CCurTopicInfo &cur)
  383. {
  384.     // If stack empty, output pre-text for the current tag & push the current
  385.     // tag onto stack.
  386.                         
  387.     if(NULL == pStack)
  388.     {
  389.         OutputFormatString(cur, pfmtPara, CFmtListPara::tagPre, ptag);
  390.             
  391.         return Push(pStack, pfmtPara, ptag);
  392.     }
  393.     
  394.     // Same tag name? Just substitute current tag information on topmost stack
  395.     // entry, then return. Don't need to output anything because tag isn't changing
  396.             
  397.     if(0 == _stricmp(pStack->ptagPara->sTag, ptag->sTag))
  398.     {
  399.         pStack->ptagPara = ptag;
  400.         return pStack;
  401.     }
  402.         
  403.     // Current level value is higher. Output pre text for current level value
  404.     // and push current level values onto stack.
  405.         
  406.     if(pfmtPara->GetLevel() > pStack->pfmtPara->GetLevel())
  407.     {
  408.         OutputFormatString(cur, pfmtPara, CFmtListPara::tagPre, ptag);
  409.                         
  410.         return Push(pStack, pfmtPara, ptag);
  411.     }
  412.         
  413.     // Current level value is same. Output post text for stack tag; switch stack
  414.     // values to use the current ones; and output pre text for current tag.
  415.         
  416.     if( pfmtPara->GetLevel() == pStack->pfmtPara->GetLevel() )
  417.     {
  418.         OutputFormatString(cur, pStack->pfmtPara, CFmtListPara::tagPost, pStack->ptagPara);
  419.                         
  420.         pStack->pfmtPara = pfmtPara;
  421.         pStack->ptagPara = ptag;
  422.                         
  423.         OutputFormatString(cur, pfmtPara, CFmtListPara::tagPre, ptag);
  424.                     
  425.         return pStack;
  426.     }
  427.         
  428.     // Current level value is lower. Output post text for stack tag; pop stack;
  429.     // and recurse (there may be more values to pop!)
  430.         
  431.     OutputFormatString(cur, pStack->pfmtPara, CFmtListPara::tagPost, pStack->ptagPara);
  432.                 
  433.     return AdjustStack(Pop(pStack), pfmtPara, ptag, cur);
  434. }
  435.  
  436.  
  437.  
  438. /*
  439. @func Outputs a single autoduck tag to the output file. The output 
  440. generation is controlled by the format information string for the tag.
  441.        
  442. @rdesc Returns a pointer to the new formatting stack.
  443. */
  444.  
  445. PFORMATSTACK OutputParagraph(
  446.     CTag             *ptag,        //@parm Tag to output.
  447.     CFmtPara         *pfmtPara,    //@parm Formatting information for tag.
  448.     CCurTopicInfo     &cur,        //@parm Topic log, output file, etc.
  449.     PFORMATSTACK     pStack)        //@parm Format/tag info stack used to track
  450.                                 //  the output state, when to print pre/post
  451. {                                //  format strings, etc.
  452.  
  453.     if(pfmtPara->GetNumFields() != ptag->nFields)
  454.     {
  455.         // Error: number of fields did not match
  456.         PrintTagError(cur, ptag, 0, warnWrongNumFields);
  457.         
  458.         // Don't return on this error, go ahead and generate some output
  459.     }
  460.     
  461.     pStack = AdjustStack(pStack, pfmtPara, ptag, cur);
  462.  
  463.     OutputFormatString(cur, pfmtPara, CFmtListPara::tagFormat, ptag);
  464.  
  465.     ptag->nState.WasOutput = TRUE;
  466.  
  467.     return pStack;
  468. }
  469.  
  470.  
  471.  
  472. //@func Outputs a single character from a format string, handling
  473. // escape sequences correctly.
  474. //
  475. //@rdesc Returns a pointer to the next character in the string.
  476.  
  477. void OutputFormatChar(
  478.     CCurTopicInfo &cur,     //@parm Output state
  479.     const char *&sz,        //@parm Front of string to output - updated with
  480.                             // new character position after output
  481.     CFmtBase *pfmt)         //@parm Parent formatting block
  482. {
  483.     CFile &fileOutput = cur.m_fileOutput;    
  484.     
  485.     switch(*sz)
  486.     {
  487.     case chCaret:               // Handle escapes
  488.         switch(sz[1])
  489.         {
  490.         case chCaret:
  491.             fileOutput.Write(&sz[1], 1);
  492.             sz += 2;
  493.             break;
  494.             
  495.         case 'n':
  496.             fileOutput.Write(szNewline, strlen(szNewline));
  497.             sz+2;
  498.             break;
  499.             
  500.         default:
  501.             fileOutput.Write(sz, 1);
  502.             PrintFmtError(cur, pfmt, warnBadFmtEscapeSequence);
  503.             sz++;
  504.             break;
  505.         }
  506.         break;
  507.         
  508.     default:
  509.         fileOutput.Write(sz, 1);
  510.         sz++;
  511.         break;
  512.     }
  513. }
  514.  
  515.  
  516. /*****************************************************************************
  517. * Format String Output and Helper Functions
  518. */
  519.  
  520. /*
  521. @func This function retrieves the next tag structure with a name
  522. matching the specified name.
  523.  
  524. @rdesc Returns the tag structure if a match is found; otherwise
  525. returns NULL.
  526. */
  527. CTag *GetNextTag(
  528.     CTagList &listTags,     //@parm Specifies a pointer to a list of tags.
  529.     const char *szTag,        //@parm Specifies the name to search for.
  530.     POSITION &posTag)       //@parm List iterator position
  531. {
  532.     CTag *ptag;
  533.  
  534.     while( posTag )
  535.     {
  536.         ptag = listTags.GetNext( posTag );
  537.  
  538.         if(_stricmp(szTag, ptag->sTag) == 0)
  539.             return ptag;
  540.     }
  541.     
  542.     return NULL;
  543. }
  544.  
  545. /*
  546. @func This function outputs a formatting string
  547. (read from the formatting file) to the output temp file. A formatting
  548. string can contain a mixture of straight RTF text and field references.
  549. */
  550. void OutputFormatString(
  551.     CCurTopicInfo &cur,     //@parm Topic environemnt information
  552.     CFmtBase *pfmt,            //@parm Formatting block to use
  553.     int  nFmt,                //@parm Which format string to output
  554.     CTag *ptag)             //@parm Tag to output
  555. {
  556.     const char *sz = pfmt->GetFmtString(nFmt);
  557.  
  558.     OutputFormatString(cur, sz, pfmt, ptag);
  559. }
  560.  
  561. /*
  562. @func This function outputs a formatting string
  563. (read from the formatting file) to the output temp file. A formatting
  564. string can contain a mixture of straight RTF text and field references.
  565. */
  566. void OutputFormatString(
  567.     CCurTopicInfo &cur,     //@parm Topic environemnt information
  568.     const char *sz,         //@parm Formatting string
  569.     CFmtBase *pfmt,            //@parm Parent formatting block
  570.     CTag *ptag)             //@parm Tag to output
  571. {
  572.     int nFieldIndex;
  573.     int nSrcLineOffset = 0;
  574.     int nRet;
  575.     
  576.     CTag *ptagRef;
  577.     
  578.     // Generate output for tag according to format info string
  579.  
  580.     while (*sz != '\0')
  581.     {
  582.         if(*sz == chDollar || *sz == chPound)
  583.         {
  584.             // Escape
  585.             
  586.             if(sz[1] == sz[0])
  587.             {
  588.                 // Handle escaped field reference character
  589.                 sz++;
  590.  
  591.                 OutputFormatChar(cur, sz, pfmt);
  592.  
  593.                 continue;
  594.             }
  595.  
  596.             // Tag field reference
  597.  
  598.             if(isdigit(sz[1]))
  599.             {
  600.                 if(NULL == ptag)
  601.                 {
  602.                     PrintFmtError(cur, pfmt, warnTagFieldUnavailable);
  603.                     sz += 2;
  604.                     continue;
  605.                 }
  606.  
  607.                 // Looks like a field reference, check digit
  608.                 nFieldIndex = atoi(sz+1) - 1;
  609.                 if(nFieldIndex > MAXNUMFIELDS || NULL == ptag->aszFieldText[nFieldIndex])
  610.                 {
  611.                     PrintError(cur.m_log.GetSrcFn(cur.m_pTopic->GetSrcFn()), 
  612.                                 ptag->lSrcLineNum, warnBadFieldReference);
  613.                     PrintFmtError(cur, pfmt, warnBadFieldReference);
  614.                 }
  615.                 else
  616.                 {
  617.                     // If field reference is valid, output field
  618.                     OutputField(cur, ptag, nFieldIndex, *sz);
  619.                 }
  620.  
  621.                 sz += 2;
  622.  
  623.                 continue;
  624.             }
  625.  
  626.             // Topic field reference
  627.             
  628.             if( isalpha(sz[1]) )
  629.             {
  630.                 if(NULL == cur.m_pTopic)
  631.                 {
  632.                     PrintFmtError(cur, pfmt, warnTopicFieldUnavailable);
  633.                     sz += 2;
  634.                     continue;
  635.                 }
  636.                 
  637.                 // Topic field reference. First see if topic tag matches, then check digit.
  638.                 
  639.                 static char szTopicFieldRefStop[] = ". \n\t0123456789";
  640.                 const char *szEnd = SeekEnd(sz+1, szTopicFieldRefStop, MAXTAGSIZE);
  641.  
  642.                 // Check basic syntax first
  643.  
  644.                 if(szEnd == NULL || !(chPeriod == szEnd[0] && isdigit(szEnd[1])) )
  645.                 {
  646.                     OutputFormatChar(cur, sz, pfmt);
  647.  
  648.                     PrintFmtError(cur, pfmt, warnBadTopicFieldReference);
  649.  
  650.                     continue;
  651.                 }
  652.  
  653.                 CString sFieldTopicName(sz+1, szEnd-sz-1);
  654.                 CTagList &listTags = cur.m_pTopic->GetTags();
  655.                 POSITION posTag = listTags.GetHeadPosition();
  656.                 if(ptagRef = GetNextTag(listTags, sFieldTopicName, posTag))
  657.                 {
  658.                     nFieldIndex = atoi(szEnd+1) - 1;
  659.                     if (nFieldIndex > MAXNUMFIELDS || NULL == ptagRef->aszFieldText[nFieldIndex])
  660.                     {
  661.                         // Invalid field reference
  662.                         PrintError(cur.m_log.GetSrcFn(cur.m_pTopic->GetSrcFn()), 
  663.                                 ptag->lSrcLineNum, warnBadTopicFieldReference);
  664.                         PrintFmtError(cur, pfmt, warnBadTopicFieldReference);
  665.                     }
  666.                     else
  667.                     {
  668.                         // If field reference is valid, output field
  669.                         OutputField(cur, ptagRef, nFieldIndex, *sz);
  670.                     }
  671.                 }
  672.  
  673.                    sz = szEnd + 2;
  674.  
  675.                 continue;
  676.             }
  677.             
  678.             // Pound symbol, not followed by a field reference - output as literal.
  679.             
  680.             if(*sz == chPound)
  681.             {
  682.                 OutputFormatChar(cur, sz, pfmt);
  683.  
  684.                 continue;
  685.             }
  686.  
  687.  
  688.             // Build information
  689.  
  690.             if (chExclamation == sz[1])
  691.             {
  692.                    nRet = OutputBuildInfo(cur, sz, ptag, pfmt);
  693.                 if(nRet)
  694.                     PrintFmtError(cur, pfmt, nRet);
  695.  
  696.                 continue;
  697.             }
  698.             
  699.             // Index
  700.             
  701.             if( chOpenBracket == sz[1] && (_strnicmp(&sz[2], "index", 5) == 0))
  702.             {
  703.                 nRet = OutputIndex(cur, pfmt, sz, ptag);
  704.                 if(nRet)
  705.                     PrintFmtError(cur, pfmt, nRet);
  706.  
  707.                 continue;
  708.             }
  709.  
  710.             // Diagram
  711.             
  712.             if (chOpenBracket == sz[1])
  713.             {
  714.                 OutputDiagram(cur, pfmt, sz, ptag);
  715.  
  716.                 continue;
  717.             }
  718.  
  719.             // Constant
  720.  
  721.             if (chOpenParen == *(sz+1))
  722.             {
  723.                 nRet = OutputConstant(cur, sz);
  724.                 if(nRet)
  725.                     PrintFmtError(cur, pfmt, nRet);
  726.  
  727.                 continue;
  728.             }
  729.  
  730.             // Invalid field reference ($ not followed by digit). Output the
  731.             // char as a literal and print a warning.
  732.  
  733.             PrintFmtError(cur, pfmt, errBadFieldRef);
  734.             
  735.             OutputFormatChar(cur, sz, pfmt);
  736.         }
  737.         
  738.         else
  739.         {
  740.             OutputFormatChar(cur, sz, pfmt);
  741.         }
  742.     }
  743. }    
  744.  
  745.  
  746. /*****************************************************************************
  747. * Field Output and Helper Functions
  748. */
  749.  
  750. //@func Outputs a character from an Autoduck source field to the output
  751. // file. Uses the token definition section in the format file to weed
  752. // out special characters such as tabs and high=ascii codes, and outptu
  753. // the appropriate information.
  754.  
  755. void OutputFieldChar(
  756.     CCurTopicInfo &cur,  //@parm Output file.
  757.     char ch,                //@parm Character to output.
  758.     TokenContexts context) //@parm Token flags
  759. {
  760.     static CFmtToken *pfmtToken = NULL;
  761.     
  762.     if(pfmtToken == NULL)
  763.         pfmtToken = (CFmtToken *)cur.m_fmt.token.Get(NULL);
  764.  
  765.     ASSERT(pfmtToken);
  766.  
  767.     CFile &fileOutput = cur.m_fileOutput;
  768.     const char *szToken;
  769.  
  770.     // Characters that must be escaped given this output type
  771.     if( pfmtToken->IsToken(ch) )
  772.     {                                 
  773.         szToken = pfmtToken->GetToken(ch, context);
  774.     
  775.         fileOutput.Write(szToken, strlen(szToken));
  776.     }
  777.     
  778.     // Output extended ASCII character
  779.     else if( ((unsigned char)ch) > 127)
  780.     {
  781.  
  782.         szToken = pfmtToken->GetHighCharMask();
  783.         char szHighChar[MAXTAGSIZE];
  784.  
  785.         sprintf(szHighChar, szToken, (unsigned char)ch);
  786.         fileOutput.Write(szHighChar, strlen(szHighChar));
  787.     }
  788.  
  789.     else
  790.     {
  791.         fileOutput.Write(&ch, 1);
  792.     }
  793. }
  794.  
  795.  
  796. /*
  797. @func Strips leading whitespace (space 
  798.     and tab characters) from all lines in the given buffer.
  799. */
  800. void StripLeadingWhiteSpace(
  801.     char *szLine)                //@parm Line to trim leading whitespace from
  802. {
  803.     char *sz;
  804.     
  805.     sz = szLine;
  806.  
  807.     while (*sz)
  808.     {
  809.         // Find first non-whitespace character in line
  810.         while (((chSpace == *sz) || (chTab == *sz)) && *sz)
  811.         {
  812.             ++sz;
  813.         }
  814.  
  815.         // Copy remainder of line (overwriting the original string)
  816.         while (*sz)
  817.         {
  818.             *szLine++ = *sz++;
  819.             if (chNewline == *(sz-1))
  820.                 break;
  821.         }
  822.     }
  823.     *szLine = '\0';  // add terminating NULL
  824. }
  825.  
  826.  
  827.  
  828.  
  829.  
  830.  
  831. /*
  832. @func void | AdjustExampleWhiteSpace | Adjusts leading whitespace
  833. for example source code lines. Adjusts whitespace by calculating
  834. the amount of leading whitespace on first non-blank line and
  835. subtracting this amount from the leading whitespace on each line.
  836.  
  837. Specify output tab size in format information file?
  838. */
  839. void AdjustExampleWhiteSpace(
  840.     char *&szField,             //@field Field to adjust
  841.     int nTabSize)                //@field Tab size to use
  842. {
  843.     char *szSrc, *szDest;
  844.     int nNumSpacesToRemove = 0;
  845.     int nNumSpacesAdjusted;
  846.     int nNumSpaces;
  847.     int nNewBufSize;
  848.     char *szNewField;
  849.     
  850.     // Set working pointers
  851.     szSrc = szField;
  852.     
  853.     // Locate first non-blank line
  854.     while (*szSrc && ((chSpace == *szSrc) || (chTab == *szSrc) || (chNewline == *szSrc)))
  855.         ++szSrc;
  856.     
  857.     // If whitespace encountered, walk back and determine 
  858.     // amount of whitespace to remove
  859.     if (szSrc > szField)
  860.     {
  861.         --szSrc;
  862.         while(1)
  863.         {
  864.             if (chSpace == *szSrc)
  865.                 ++nNumSpacesToRemove;
  866.             else if (chTab == *szSrc)
  867.                 nNumSpacesToRemove += nTabSize;
  868.             else if (chNewline == *szSrc)
  869.             {
  870.                 // At least one blank line must have preceded, maintain 
  871.                 ++szSrc;
  872.                 break;
  873.             }
  874.             else
  875.             {
  876.                 ASSERT(0);
  877.             }
  878.             
  879.             // Don't walk past beginning of buffer
  880.             if(szSrc == szField)
  881.                 break;
  882.             else
  883.                 --szSrc;
  884.         }
  885.     }
  886.     else    // No whitespace in first line
  887.     {
  888.         return;
  889.     }
  890.     
  891.     // Allocate a new buffer
  892.     nNewBufSize = 2 * strlen(szField);
  893.     szNewField = new char[nNewBufSize];
  894.     ZeroMem(szNewField, nNewBufSize);
  895.  
  896.     szDest = szNewField;
  897.  
  898.     // Walk the buffer, adjusting leading whitespace for each line
  899.     while (*szSrc)
  900.     {
  901.         // Count leading whitespace in line
  902.         nNumSpaces = 0;
  903.         while ((chSpace == *szSrc) || (chTab == *szSrc))
  904.         {
  905.             if (chSpace == *szSrc)
  906.                 ++nNumSpaces;
  907.             else if (chTab == *szSrc)
  908.                 nNumSpaces += nTabSize;
  909.             
  910.             ++szSrc;
  911.         }
  912.  
  913.         // Output adjusted whitespace
  914.         // Note: MSGRIDA1 tab=4
  915.         // Maybe output tab size should be specified by format info?
  916.         nNumSpacesAdjusted = nNumSpaces - nNumSpacesToRemove;
  917.         if (nNumSpacesAdjusted > 0)
  918.         {
  919.             while (nNumSpacesAdjusted > 0)
  920.             if (nNumSpacesAdjusted >= nTabSize)
  921.             {
  922.                 *szDest++ = chTab;
  923.                 nNumSpacesAdjusted -= nTabSize;
  924.             }
  925.             else
  926.             {
  927.                 *szDest++ = chSpace;
  928.                 --nNumSpacesAdjusted;
  929.             }
  930.         }
  931.         
  932.         // Copy remainder of line
  933.         while (*szSrc)
  934.         {
  935.             *szDest++ = *szSrc++;
  936.             if (chNewline == *(szSrc-1))
  937.                 break;
  938.         }
  939.     }
  940.  
  941.     *szDest = '\0';  // add terminating NULL
  942.  
  943.     // Free original buffer
  944.     delete szField;
  945.  
  946.     szField = szNewField;
  947. }
  948.  
  949.  
  950.  
  951.  
  952.  
  953. /*
  954. @func Converts the specified string to a valid WinHelp context string.
  955. Valid characters include:
  956.  
  957. - alphanumerics
  958.  
  959. - underscores
  960.  
  961. Spaces and colons are converted to underscores.
  962.  
  963. High-ascii characters are converted to the form ".xx" where
  964. "xx" is the hexadecimal value for the character.
  965.  
  966. Template specifications are stripped.
  967. */
  968. void MakeContextString(
  969.     char *szContext,        // @parm Destination buffer of at least 256 bytes
  970.     const char *szText)     // @parm Source string.
  971. {
  972.     char szCode[6];
  973.     int i, j;
  974.     const char *szT;
  975.  
  976.     ASSERT(szText);
  977.  
  978.     // @comm Context string is generated using the specified string.
  979.     // Legal context string characters in the string are copied exactly.
  980.     // Spaces and colons are converted to underscores.
  981.     // Other characters are converted to a period, followed by the hex
  982.     // number for the character.
  983.  
  984.     for(i = 0; i < 255 && *szText; szText++)
  985.     {
  986.         if(chBackslash == *szText)
  987.         {
  988.             // Skip over backslashes
  989.         }
  990.         else if(isalnum(*szText) || chUnderscore == *szText || chPeriod == *szText)
  991.         {
  992.             // Legal characters as is
  993.  
  994.             szContext[i++] = *szText;
  995.         }
  996.         else if(chSpace == *szText || chColon == *szText)
  997.         {
  998.             szContext[i++] = chUnderscore;
  999.         }
  1000.         else
  1001.         {
  1002.             // If this is a C++ template (matching angle brackets), then
  1003.             // we lop off the template arguments
  1004.  
  1005.             if(chOpenAngle == *szText)
  1006.             {
  1007.                 szT = MatchParen(szText, chCloseAngle);
  1008.                 if(*szT)
  1009.                     break;
  1010.             }
  1011.  
  1012.             
  1013.             // illegal chars converted to ".xx"
  1014.             szCode[0] = chPeriod;
  1015.             _itoa((unsigned char)*szText, &szCode[1], 16);
  1016.  
  1017.             for(j = 0; szCode[j] && i < 255; j++, i++)
  1018.                 szContext[i] = szCode[j];
  1019.         }
  1020.     }
  1021.     szContext[i] = 0;
  1022. }
  1023.  
  1024.  
  1025.  
  1026.  
  1027. /*-----------------------------------------------------------------------
  1028. @func Outputs a single autoduck field to a 
  1029. given temporary file. Performs some processing of newline
  1030. characters (see comments) and calls <f OutputSpecial> to expand
  1031. type/special formatting characters.
  1032.         
  1033. @comm If field is example code, <f OutputField> calls 
  1034. <f AdjustExampleWhiteSpace> function to
  1035. clean up indentation. This will cause the memory block containing the
  1036. field text to be reallocated. <f OutputField> adjusts the
  1037. <t TAG> structure by storing the address of the new memory block. 
  1038.  
  1039. If a single newline character is followed by additional text, 
  1040. <f OutputField> inserts a space character before the 
  1041. newline<em->trailing whitespace has already been stripped from 
  1042. all lines.
  1043. */
  1044. void OutputField(
  1045.     CCurTopicInfo &cur,     //@parm Topic state info
  1046.     CTag *ptag,             //@parm Parent tag of field
  1047.     int  nField,            //@parm Which field to output
  1048.     char  cFieldRef)        //@parm Field reference character:
  1049.                             //  @flag $ | Output contents of field normally.
  1050.                             //  @flag # | Convert to context string.
  1051. {
  1052.     char *szField;
  1053.     char szContext[MAXCONTEXT+1];
  1054.     int nSrcLineOffset = 0;
  1055.  
  1056.     // Validate that there is a field to output
  1057.     if (NULL == ptag->aszFieldText[nField])
  1058.         return;
  1059.  
  1060.     // Strip/adjust leading whitespace
  1061.     if (ptag->nState.IsExample && nField+1 == ptag->nFields)
  1062.     {
  1063.         AdjustExampleWhiteSpace(ptag->aszFieldText[nField], 
  1064.                                     cur.m_run.nExampleTabSize);
  1065.     }
  1066.     else
  1067.     {
  1068.         StripLeadingWhiteSpace(ptag->aszFieldText[nField]);
  1069.     }
  1070.  
  1071.     // Process context strings first - easy.
  1072.  
  1073.     if (chPound == cFieldRef)
  1074.     {
  1075.         MakeContextString(szContext, ptag->aszFieldText[nField]);
  1076.         
  1077.         szField = szContext;
  1078.  
  1079.         cur.m_fileOutput.Write(szContext, strlen(szContext));
  1080.  
  1081.         return;
  1082.     }
  1083.  
  1084.     // Process $ fields now.
  1085.     
  1086.     szField = ptag->aszFieldText[nField];
  1087.         
  1088.     // Output contents of field
  1089.     while (*szField)
  1090.     {
  1091.         // Look for newline character
  1092.         if(chNewline == *szField)
  1093.         {
  1094.             // Example field - insert paragraph after each newline.
  1095.  
  1096.             if(ptag->nState.IsExample && nField+1 == ptag->nFields)
  1097.             {
  1098.                 // Single newline wraps example source code
  1099.  
  1100.                 OutputFieldChar(cur, chNewline, ctxExample);
  1101.                 cur.m_fileOutput.Write(szNewline, strlen(szNewline));
  1102.             }
  1103.             
  1104.             // Two newlines - insert a paragraph for regular fields.
  1105.  
  1106.             else if(chNewline == szField[1])
  1107.             {
  1108.                 OutputFieldChar(cur, chNewline);
  1109.                 cur.m_fileOutput.Write(szNewline, strlen(szNewline));
  1110.  
  1111.                 szField++;
  1112.                 nSrcLineOffset++;
  1113.             }
  1114.             
  1115.             // Single newline followed by text - just a space
  1116.  
  1117.             else if(szField[1] != '\0')
  1118.             {
  1119.                 cur.m_fileOutput.Write(" ", 1);
  1120.                 cur.m_fileOutput.Write(szNewline, strlen(szNewline));
  1121.             }
  1122.  
  1123.             // If field ends here, don't write anything.
  1124.  
  1125.             ++szField;
  1126.             ++nSrcLineOffset;
  1127.  
  1128.             continue;
  1129.         }
  1130.  
  1131.         // Look for escaped characters
  1132.         if (chBackslash == *szField)
  1133.         {
  1134.             if(ptag->nState.IsExample && nField+1 == ptag->nFields)
  1135.             {
  1136.                 // The only significant escaped characters in example
  1137.                 // code are forward slash (to allow comments)
  1138.                 // and '@' to allow autoduck source examples
  1139.  
  1140.                 switch(szField[1])
  1141.                 {
  1142.                 // Escape characters
  1143.  
  1144.                 case chSlash:
  1145.                 case chAmpersand:
  1146.                     
  1147.                     ++szField;
  1148.                     OutputFieldChar(cur, *szField++);
  1149.  
  1150.                     break;
  1151.  
  1152.                 default:
  1153.  
  1154.                 // Output the backslash as is.
  1155.  
  1156.                     OutputFieldChar(cur, *szField++);
  1157.  
  1158.                     break;
  1159.                 }
  1160.  
  1161.                 continue;
  1162.             }
  1163.             
  1164.             // Other field references can contain escaped type 
  1165.             // or special formatting characters
  1166.  
  1167.             if(strchr(szAutoduckEscape, szField[1]))
  1168.             {
  1169.                 // Output escaped character
  1170.  
  1171.                 ++szField;
  1172.                 OutputFieldChar(cur, *szField++);
  1173.             }
  1174.             else
  1175.             {
  1176.                 // Output backslash as is.
  1177.  
  1178.                 OutputFieldChar(cur, *szField++);
  1179.             }
  1180.  
  1181.             continue;
  1182.  
  1183.         }
  1184.  
  1185.         // Look for type or special formatting
  1186.         // (not allowed within example source code and context strings)
  1187.  
  1188.         if ((chOpenAngle == *szField) && 
  1189.             !(ptag->nState.IsExample && nField+1 == ptag->nFields))
  1190.         {
  1191.             // Handle special character and type formatting
  1192.             OutputTextTag(cur, ptag, szField, nSrcLineOffset);
  1193.  
  1194.             continue;
  1195.         }
  1196.  
  1197.         // Output character
  1198.  
  1199.         OutputFieldChar(cur, *szField++);
  1200.     }
  1201. }
  1202.  
  1203. /*****************************************************************************
  1204. * Text Tag Output & Helper Functions
  1205. */
  1206.  
  1207.  
  1208. /*
  1209. @func This function extracts the fields of a text tag.
  1210.  
  1211. @rdesc Returns zero if successful or an error code.
  1212. */
  1213.  
  1214. int ExtractTextTag(
  1215.     const char *&sz,           //@parm Points to start of text tag name.
  1216.                                 // Value updated with ending tag position.
  1217.     CTag &tagText,             //@parm TAG structure to fill with text fields
  1218.     int &nSrcLineOffset)       //@parm Offset from start of parent
  1219. {
  1220.     int i, j, k, nFieldLen;
  1221.     const char *szField;
  1222.     const char *szEnd;
  1223.  
  1224.     // Find the end of the text tag.
  1225.     
  1226.     szEnd = sz;
  1227.     do
  1228.     {
  1229.         if(*szEnd == chCloseAngle)
  1230.         {
  1231.             if(!(szEnd != sz && *(szEnd-1) == chBackslash))
  1232.                 break;
  1233.         }
  1234.  
  1235.     } while(*++szEnd);
  1236.  
  1237.     if(*szEnd != chCloseAngle)
  1238.         return warnMissingTextTagTerminator;
  1239.  
  1240.     // Grab the tag fields.
  1241.  
  1242.     szField = sz;
  1243.  
  1244.     while(*szField && *szField != chCloseAngle)
  1245.     {
  1246.         if( MAXNUMFIELDS == tagText.nFields )
  1247.             return warnTooManyTextFields;
  1248.  
  1249.         // Add the field to the tag. Allocate a block of 
  1250.         // memory for the field equal to the remaining
  1251.         // text.
  1252.         
  1253.         k = tagText.nFields;
  1254.  
  1255.         szField = EatWhite(szField);
  1256.         
  1257.         nFieldLen = szEnd - szField;
  1258.  
  1259.         tagText.aszFieldText[k] = new char[nFieldLen+1];
  1260.  
  1261.         ZeroMem(tagText.aszFieldText[k], nFieldLen+1);
  1262.         
  1263.         // Add characters to the field through the end (a field separator
  1264.         // or the end of the text tag).
  1265.         //
  1266.         // K is the field number
  1267.         // j is the character number within the field.
  1268.         // szField is updated to the next character to be added.
  1269.         
  1270.         j = 0;
  1271.         int nContinueField = TRUE;
  1272.  
  1273.         while(nContinueField)
  1274.         {
  1275.             switch(*szField)
  1276.             {
  1277.             case chColon:
  1278.                 if(*(szField+1) == chColon)
  1279.                     szField++;
  1280.  
  1281.             case chPeriod:
  1282.                 
  1283.                 // Next character.
  1284.  
  1285.                 szField++;
  1286.  
  1287.                 // fall through - for angle bracket we don't want to advance
  1288.                 // past it, because it terminates the outermost loop.
  1289.  
  1290.             case chCloseAngle:
  1291.  
  1292.                 // next field
  1293.  
  1294.                 tagText.nFields++;
  1295.  
  1296.                 // Break out of loop.
  1297.  
  1298.                 nContinueField = FALSE;
  1299.  
  1300.                 break;
  1301.             
  1302.             case chBackslash:
  1303.  
  1304.                 switch(*(szField+1))
  1305.                 {
  1306.                 // These are text-tag-specific escapes, and need to be converted
  1307.                 // before handing the fields over to the format string outputter.
  1308.  
  1309.                 case chCloseAngle:
  1310.                 case chPeriod:
  1311.                 case chColon:
  1312.  
  1313.                     tagText.aszFieldText[k][j++] = *(szField+1);
  1314.                     szField += 2;
  1315.                     break;
  1316.  
  1317.                 // These are not converted, the backslash is passed in to the
  1318.                 // field and ends up being parsed by <f OutputFormatString>.
  1319.  
  1320.                 default:
  1321.  
  1322.                     tagText.aszFieldText[j][i++] = *szField++;
  1323.                     tagText.aszFieldText[j][i++] = *szField++;
  1324.                     break;
  1325.                 }
  1326.  
  1327.             break;
  1328.  
  1329.             case chNewline:
  1330.                 
  1331.                 nSrcLineOffset++;
  1332.  
  1333.                 // fall through
  1334.            
  1335.             default:
  1336.                 tagText.aszFieldText[k][j++] = *szField++;
  1337.  
  1338.                 break;
  1339.             }
  1340.         }
  1341.     }
  1342.     
  1343.     sz = ++szEnd;
  1344.     
  1345.     return 0;
  1346. }
  1347.  
  1348.  
  1349. /*-----------------------------------------------------------------------
  1350. @func Parses a text tag and outputs the specified format string to the
  1351. temporary file.
  1352.  
  1353. @parm Current topic state.
  1354.  
  1355. @parm Parent paragraph or topic tag for the text tag.
  1356.  
  1357. @parm Points to the beginning of the text tag, on the opening angle
  1358. bracket. Updated with the ending position of the text tag (one character
  1359. beyond the closing angle bracket).
  1360.  
  1361. @parm Offset of text tag from start of parent tag.
  1362.  
  1363. */
  1364.  
  1365. void OutputTextTag(CCurTopicInfo &cur, CTag *ptag, const char *&szField,
  1366.     int &nSrcLineOffset) 
  1367. {
  1368.     CTag tagText;
  1369.     char szTag[MAXTAGSIZE+1];
  1370.  
  1371.     int i;
  1372.     
  1373.     CFmtSrchText srchText;
  1374.     CFmtText    *pfmtText;
  1375.     
  1376.     // parser assumes we have an open angle bracket
  1377.     ASSERT(chOpenAngle == *szField);
  1378.  
  1379.     // get the tag name.
  1380.  
  1381.     szField = EatWhite(++szField);
  1382.     for(i = 0; istagchar(szField[i]) && i < MAXTAGSIZE; i++)
  1383.         szTag[i] = szField[i];
  1384.  
  1385.     if( i == 0 || (i == MAXTAGSIZE && istagchar(szField[i])) )
  1386.     {
  1387.         PrintTagError(cur, ptag, nSrcLineOffset, fmterrBadTagName);
  1388.  
  1389.         return;        
  1390.     }
  1391.     szTag[i] = '\0';
  1392.  
  1393.     // Initialize pseudo-TAG structure
  1394.     
  1395.     tagText.sTag = szTag;
  1396.     tagText.lSrcLineNum = ptag->lSrcLineNum + nSrcLineOffset;
  1397.     
  1398.     // Try to extract the text fields.
  1399.     
  1400.     szField += i;
  1401.     int nSrcLineSave = nSrcLineOffset;
  1402.     int nRet = ExtractTextTag(szField, tagText, nSrcLineOffset);
  1403.     if(nRet)
  1404.     {
  1405.         PrintTagError(cur, ptag, nSrcLineOffset, nRet);
  1406.  
  1407.         return;
  1408.     }
  1409.     
  1410.     // get the format string.
  1411.  
  1412.     srchText.m_sName = szTag;
  1413.     srchText.m_pLog = &cur.m_log;
  1414.     srchText.m_ptagTag = &tagText;
  1415.     srchText.m_plistTags = &cur.m_pTopic->GetTags();
  1416.  
  1417.     pfmtText = (CFmtText *)cur.m_fmt.text.Get(&srchText);
  1418.     if(NULL == pfmtText)
  1419.     {
  1420.         PrintTagError(cur, ptag, nSrcLineSave, warnNoTextFormat);
  1421.  
  1422.         return;
  1423.     }
  1424.  
  1425.     if(pfmtText->GetNumFields() != tagText.nFields)
  1426.     {
  1427.         PrintTagError(cur, ptag, nSrcLineSave, warnWrongNumTextFields);
  1428.         return;
  1429.     }
  1430.  
  1431.     // Generate output according to format string
  1432.     
  1433.     OutputFormatString(cur, pfmtText, CFmtListText::tagFormat, &tagText);
  1434. }
  1435.  
  1436.  
  1437.  
  1438. /*****************************************************************************
  1439. * Build Info
  1440. */
  1441.  
  1442.  
  1443. /*
  1444. @func This function outputs a special build-information field, such as the current filename,
  1445. date, or time.
  1446.  
  1447. @rdesc Returns a pointer to the character following the build information field.
  1448. */
  1449. int OutputBuildInfo(
  1450.     CCurTopicInfo &cur,                //@parm Output state including topic.
  1451.     const char *&szField,           //@parm Build info specifier.
  1452.     CTag *ptag,                        //@parm Tag information structure (can be NULL). Used
  1453.                                     // for error reporting.
  1454.     CFmtBase *pfmt)                 //@parm Format tag structure, used for error reporting.
  1455. {
  1456.     char szChar[MAXCONTEXT+1];
  1457.     char szFname[_MAX_FNAME+1];
  1458.     char szExt[_MAX_EXT+1];
  1459.  
  1460.     CFile &fileOutput = cur.m_fileOutput;
  1461.  
  1462.     int nRet = 0;
  1463.  
  1464.     char *sz;
  1465.  
  1466.     szField+=2;
  1467.  
  1468.     szChar[0] = '\0';
  1469.         
  1470.     switch(*szField)
  1471.     {
  1472.     case 'D':
  1473.     case 'd':
  1474.         // output the date
  1475.         _strdate(szChar);
  1476.  
  1477.         break;
  1478.         
  1479.     case 'l':
  1480.     case 'L':
  1481.     {
  1482.         CTag *pLineTag = NULL;
  1483.  
  1484.         // output the tag line number - use the tag or if no tag,
  1485.         // use the topic.
  1486.  
  1487.         if(ptag)
  1488.         {
  1489.             pLineTag = ptag;
  1490.         }
  1491.         else if(cur.m_pTopic)
  1492.         {
  1493.             // this is NULL if empty topic from log file
  1494.  
  1495.             pLineTag = cur.m_pTopic->GetTopicTag();
  1496.         }
  1497.  
  1498.         sprintf(szChar, "%ld", pLineTag->lSrcLineNum);
  1499.         
  1500.         break;
  1501.     }
  1502.  
  1503.     case 'f': 
  1504.     case 'F':
  1505.         // Output file name
  1506.         if(cur.m_pTopic)
  1507.         {
  1508.             _fullpath(szChar, cur.m_log.GetSrcFn(cur.m_pTopic->GetSrcFn()), _MAX_PATH);
  1509.             _splitpath(szChar, NULL, NULL, szFname, szExt);
  1510.  
  1511.             strcpy(szChar, szFname);
  1512.             strcat(szChar, szExt);
  1513.         }
  1514.         break;
  1515.                                                                                                                       
  1516.     case 'p':
  1517.     case 'P':
  1518.         // Output full path name
  1519.         if(cur.m_pTopic)
  1520.         {
  1521.             _fullpath(szChar, cur.m_log.GetSrcFn(cur.m_pTopic->GetSrcFn()), _MAX_PATH);
  1522.             
  1523.             // Convert forward slashes to back slashes - more
  1524.             // universally accepted
  1525.  
  1526.             for(int i = 0; szChar[i]; i++)
  1527.             {
  1528.                 if(szChar[i] == chBackslash)
  1529.                     szChar[i] = chSlash;
  1530.             }
  1531.         }
  1532.         break;
  1533.         
  1534.     case 'n':
  1535.     case 'N':
  1536.         if(cur.m_pTopic)
  1537.             strcpy(szChar, cur.m_pTopic->GetName());
  1538.         break;
  1539.  
  1540.     case 'c':
  1541.     case 'C':
  1542.         if(cur.m_pTopic)
  1543.             MakeContextString(szChar, cur.m_pTopic->GetContext());
  1544.         break;
  1545.  
  1546.     case 'e':
  1547.     case 'E':
  1548.     {
  1549.         int nDocTag;
  1550.         const char *szDocTag;
  1551.  
  1552.         // get the tag number from the topic then retrieve that
  1553.         // text from the topic log. the doc tag is truncated if greater
  1554.         // than 256 characters (MAXCONTEXT).
  1555.  
  1556.         if(cur.m_pTopic)
  1557.         {
  1558.             nDocTag = cur.m_pTopic->GetDocTag();
  1559.             if(nDocTag != -1)
  1560.             {
  1561.                 szDocTag = cur.m_log.GetDocTag(nDocTag);
  1562.                 strncpy(szChar, szDocTag, MAXCONTEXT);
  1563.                 szChar[MAXCONTEXT] = '\0';
  1564.             }
  1565.  
  1566.             szDocTag = cur.m_pTopic->GetLocalDocTag();
  1567.             if(*szDocTag)
  1568.             {
  1569.                 nDocTag = MAXCONTEXT - strlen(szDocTag) - strlen(szChar);
  1570.  
  1571.                 if(nDocTag >= 0)
  1572.                     strcat(szChar, szDocTag);
  1573.             }
  1574.         }
  1575.  
  1576.         break;
  1577.     }
  1578.  
  1579.     default:
  1580.  
  1581.         nRet = warnUnknownBuildConstant;
  1582.  
  1583.         break;
  1584.     }
  1585.  
  1586.     if(isupper(*szField))
  1587.         _strupr(szChar);
  1588.  
  1589.     if(!nRet)
  1590.     {
  1591.         for(sz = szChar; *sz; sz++)
  1592.             OutputFieldChar(cur, *sz);
  1593.         szField++;
  1594.     }
  1595.  
  1596.     return nRet;
  1597. }
  1598.  
  1599.  
  1600. /*****************************************************************************
  1601. * Constants
  1602. */
  1603.  
  1604. /*
  1605. @func This function parses a constant reference in a formatting
  1606. string, and outputs the named constant.
  1607.  
  1608. @rdesc Pointer to the first character following the constant
  1609. reference in the formatting string.
  1610. */
  1611.  
  1612. int OutputConstant(
  1613.     CCurTopicInfo &cur, 
  1614.     const char *&sz)
  1615. {
  1616.     const char *szEnd;
  1617.     static char szConstNameTerm[] = ")\n\r \t";
  1618.  
  1619.     CFmtSrchConst srch;
  1620.     CFmtConst *pfmt;
  1621.  
  1622.     sz += 2;
  1623.  
  1624.     szEnd = SeekEnd(sz, szConstNameTerm, MAXTAGSIZE);
  1625.     if(szEnd == NULL || *szEnd != chCloseParen)
  1626.         return warnBadConstantReference;
  1627.     
  1628.     CString sConstName(sz, szEnd-sz);
  1629.     srch.m_sName = sConstName;
  1630.  
  1631.     pfmt = (CFmtConst *)cur.m_fmt.constant.Get(&srch);
  1632.     if(NULL == pfmt)
  1633.         return warnNoConstFormat;
  1634.     
  1635.     CString &sFmt = pfmt->GetFmtString(CFmtListConst::tagDefine);
  1636.  
  1637.     cur.m_fileOutput.Write(sFmt, sFmt.GetLength());
  1638.  
  1639.     sz = szEnd+1;
  1640.  
  1641.     return 0;
  1642. }
  1643.  
  1644.  
  1645. /*****************************************************************************
  1646.  * Diagram Output & Helper Functions
  1647.  */
  1648.  
  1649.  
  1650. /*
  1651. @func This function checks to see if a tag with a specified name appears
  1652. in the topic.
  1653.  
  1654. @rdesc Returns TRUE if the tag is found.
  1655. */
  1656.  
  1657. int HasNullParas(
  1658.     CTagList  &listTags,         //@parm Specifies the first tag in the list.
  1659.     CStrArray &asParas)         //@parm Specifies a list of names to look for.
  1660. {
  1661.     CTag *ptagT;
  1662.     int  nLen = asParas.GetSize();
  1663.     int  i;
  1664.     POSITION posTag = listTags.GetHeadPosition();
  1665.     while(posTag)
  1666.     {
  1667.         ptagT = listTags.GetNext(posTag);
  1668.  
  1669.         for(i = 0; i < nLen; i++)
  1670.         {
  1671.             if(_strcmpi(*asParas[i], ptagT->sTag) == 0)
  1672.                 return TRUE;
  1673.         }
  1674.     }
  1675.     return FALSE;
  1676. }
  1677.  
  1678.  
  1679. /*
  1680.  
  1681. @func This function outputs a syntax diagram.
  1682.  
  1683. */
  1684. void OutputDiagram(
  1685.     CCurTopicInfo &cur,     //@parm Topic output state information
  1686.     CFmtBase *pfmtParent,   //@parm Parent formatting tag
  1687.     const char *&sz,        //@parm Beginning of diagram spec in format string
  1688.     CTag *ptagParent)       //@parm Parent tag
  1689. {
  1690.     CTagList &listTags = cur.m_pTopic->GetTags(); 
  1691.  
  1692.     const char *szName;
  1693.     static char szDiagramNameTerm[] = "]\n\r \t";
  1694.  
  1695.     // Get the diagram name.
  1696.     sz = EatWhite(sz+2);
  1697.  
  1698.     szName = sz;
  1699.     while(istagchar(*sz))
  1700.         sz++;
  1701.  
  1702.     if(szName == sz || *sz != chCloseBracket)
  1703.     {
  1704.         PrintFmtError(cur, pfmtParent, warnBadDiagramReference);
  1705.         return;
  1706.     }
  1707.  
  1708.  
  1709.     // Construct the diagram name.
  1710.  
  1711.     CString sDiagramName(szName, sz-szName);
  1712.  
  1713.     // advance past the close parenthesis.
  1714.  
  1715.     sz ++;
  1716.  
  1717.     // Find the diagram formatting string.
  1718.  
  1719.     CFmtSrchTag srch;
  1720.     CFmtDiagram *pfmt;
  1721.  
  1722.     srch.m_sName = sDiagramName;
  1723.     srch.m_pLog  = &cur.m_log;
  1724.     srch.m_ptagTag = ptagParent;
  1725.     srch.m_plistTags = &cur.m_pTopic->GetTags();
  1726.  
  1727.     pfmt = (CFmtDiagram *)cur.m_fmt.diagram.Get(&srch);
  1728.     if(NULL == pfmt)
  1729.     {
  1730.         PrintFmtError(cur, pfmtParent, warnNoDiagramFormat);
  1731.         return;
  1732.     }
  1733.  
  1734.     // See if there are any "poision paragraphs" in this topic.
  1735.     
  1736.     if(HasNullParas(listTags, pfmt->GetNullParas()))
  1737.         return;
  1738.  
  1739.     OutputFormatString(cur, pfmt, CFmtListDiagram::tagPre, ptagParent);
  1740.     
  1741.     // Output the first tag
  1742.     POSITION posTag = listTags.GetHeadPosition();
  1743.     CTag *ptag = GetNextTag(listTags, pfmt->GetDiagramItem(), posTag);
  1744.     CTag *ptagNext;
  1745.  
  1746.     if(ptag)
  1747.     {
  1748.         CString &sFmtFirst = pfmt->GetFmtString(CFmtListDiagram::tagFormatFirst);
  1749.         if(!sFmtFirst.IsEmpty())
  1750.             OutputFormatString(cur, pfmt, CFmtListDiagram::tagFormatFirst, ptag);
  1751.         else
  1752.             OutputFormatString(cur, pfmt, CFmtListDiagram::tagFormat, ptag);
  1753.  
  1754.         // Output the middle tags, if any
  1755.  
  1756.         ptag = GetNextTag(listTags, pfmt->GetDiagramItem(), posTag);
  1757.         while( ptag && (ptagNext = GetNextTag(listTags, pfmt->GetDiagramItem(), posTag))  )
  1758.         {
  1759.             OutputFormatString(cur, pfmt, CFmtListDiagram::tagFormat, ptag);
  1760.             ptag = ptagNext;
  1761.         }
  1762.         
  1763.         // Output the last tag, if one exists
  1764.         if(ptag)
  1765.         {
  1766.             CString &sFmtLast = pfmt->GetFmtString(CFmtListDiagram::tagFormatLast);
  1767.             if(!sFmtLast.IsEmpty())
  1768.                 OutputFormatString(cur, pfmt, CFmtListDiagram::tagFormatLast, ptag);
  1769.             else
  1770.                 OutputFormatString(cur, pfmt, CFmtListDiagram::tagFormat, ptag);
  1771.         }
  1772.     }
  1773.         
  1774.     OutputFormatString(cur, pfmt, CFmtListDiagram::tagPost, ptagParent);
  1775. }
  1776.  
  1777.  
  1778. /*****************************************************************************
  1779.  * Index Output & Helper Functions
  1780.  */
  1781.  
  1782.  
  1783. //@func Evaluate topic to determine whether to include it in the
  1784. // index. Can evaluate tag name and extraction flags.
  1785.  
  1786. BOOL UseTopicInIndex(
  1787.     const char *szTag,         //@parm Tag name for topic
  1788.     const char *szFlags,     //@parm Flags for topic
  1789.     const char *szLocalFlags,//@parm Local topic flags (combine with above)
  1790.     CExprToken &exprTags,    //@parm Topic tag name filter
  1791.     CExprToken &exprFlags)    //@parm Topic doc flags filter
  1792. {
  1793.     CString sTagCopy;
  1794.     CString sFlagCopy;
  1795.  
  1796.     // Compare tags...
  1797.  
  1798.     CopyFlags(sTagCopy, szTag);
  1799.  
  1800.     sFlagCopy = szFlags;
  1801.     sFlagCopy += ' ';
  1802.     sFlagCopy += szLocalFlags;
  1803.  
  1804.     return exprTags.Eval(sTagCopy) && exprFlags.Eval(sFlagCopy);
  1805. }
  1806.  
  1807.  
  1808. /*
  1809. @func void | OutputWinHelpIndex | Generates a Help index
  1810.     from the list of topic logs.
  1811.  
  1812. @parm PTOPICLOGHEAD | phead | Specifies a pointer to a
  1813.     <t TOPICLOGHEAD> structure containing the list of
  1814.     topic log structures.
  1815.     
  1816. @parm FILE * | fh | Specifies the file handle of the output file.
  1817.     
  1818. @rdesc Void.
  1819. */
  1820. int OutputIndex(
  1821.     CCurTopicInfo &cur,     //@parm Topic output state
  1822.     CFmtBase *pfmtParent,   //@parm Parent formatting tag
  1823.     const char *&sz,        //@parm Index specification
  1824.     CTag *ptagParent)       //@parm Parent formatting tag
  1825. {
  1826.     int nRet;
  1827.     CMemFile        memfile;
  1828.     CCurTopicInfo   curIndex(cur.m_pTopic, memfile, cur.m_fmt, cur.m_run, cur.m_log);
  1829.  
  1830.     // Advance to "index" keyword
  1831.  
  1832.     sz += 2;
  1833.  
  1834.     // Get the format information.
  1835.     CFmtIndex *pfmt = (CFmtIndex *)cur.m_fmt.index.Get(NULL);
  1836.     if(NULL == pfmt)
  1837.         return warnNoIndexFormat;
  1838.  
  1839.     // Look for "index" keyword
  1840.  
  1841.     if(_strnicmp(sz, "index", 5))
  1842.         return warnBadIndexReference;
  1843.  
  1844.     // Mark start of tag specification, if present
  1845.  
  1846.     const char *szTags = NULL;
  1847.     const char *szTagsEnd = NULL;
  1848.     const char *szFlags = NULL;
  1849.     const char *szFlagsEnd = NULL;
  1850.     static char szTagsTerm[] = ":]";
  1851.  
  1852.     sz = EatWhite(sz+5);    // Advance past "index"
  1853.  
  1854.     if(chColon == *sz)
  1855.     {
  1856.         szTags = ++sz;
  1857.  
  1858.         // Find the end of the tag field.
  1859.     
  1860.         szTagsEnd = SeekEnd(szTags, szTagsTerm, strlen(szTags));
  1861.         if(NULL == szTagsEnd)
  1862.             return warnBadIndexReference;
  1863.  
  1864.         sz = szTagsEnd;
  1865.     }
  1866.  
  1867.     // if both are null this will be an empty string
  1868.     CString sTagsExpr(szTags, szTagsEnd-szTags);
  1869.     char *szExprBuf;
  1870.  
  1871.     // Construct the tags expression. Print the format string to a memory file,
  1872.     // then get the information back into sTagsExpr to set into the index
  1873.     // expression.
  1874.  
  1875.     memfile.SetLength(0);
  1876.  
  1877.     OutputFormatString(curIndex, sTagsExpr, pfmtParent, ptagParent);
  1878.     
  1879.     memfile.Seek(0, CFile::begin);
  1880.     DWORD dwLen = memfile.GetLength();
  1881.     szExprBuf = sTagsExpr.GetBufferSetLength((int)(dwLen+1));
  1882.     memfile.Read(szExprBuf, (UINT)dwLen);
  1883.     szExprBuf[dwLen] = '\0';
  1884.     sTagsExpr.ReleaseBuffer();
  1885.  
  1886.     // Try to mark the flags spec.
  1887.     
  1888.     if(chColon == *sz)
  1889.     {
  1890.         sz = szFlags = EatWhite(++sz);
  1891.  
  1892.         szFlagsEnd = SeekEnd(szFlags, chCloseBracket, strlen(szFlags));
  1893.         if(NULL == szFlagsEnd)
  1894.             return warnBadIndexReference;
  1895.  
  1896.         sz = szFlagsEnd;
  1897.     }
  1898.  
  1899.    
  1900.     // Construct the flags expression. Print the format string to a memory file,
  1901.     // then get the information back into sFlagsExpr to set into the index
  1902.     // expression.
  1903.  
  1904.     CString sFlagsExpr(szFlags, szFlagsEnd-szFlags);
  1905.  
  1906.     memfile.SetLength(0);
  1907.  
  1908.     OutputFormatString(curIndex, sFlagsExpr, pfmtParent, ptagParent);
  1909.     
  1910.     memfile.Seek(0, CFile::begin);
  1911.     dwLen = memfile.GetLength();
  1912.     szExprBuf = sFlagsExpr.GetBufferSetLength((int)(dwLen+1));
  1913.     memfile.Read(szExprBuf, (UINT)dwLen);
  1914.     szExprBuf[dwLen] = '\0';
  1915.     sFlagsExpr.ReleaseBuffer();
  1916.     memfile.SetLength(0);
  1917.     memfile.Close();
  1918.  
  1919.     // Should be at the closing bracket now.
  1920.  
  1921.     if(chCloseBracket != *sz)
  1922.         return warnBadIndexReference;
  1923.  
  1924.     sz++;
  1925.  
  1926.     // Set the extract flags.
  1927.  
  1928.     CExprToken exprTags;
  1929.     CExprToken exprFlags;
  1930.     const char *szCont;
  1931.  
  1932.     exprTags.UseCommasAsOr();
  1933.     exprFlags.UseCommasAsOr();
  1934.  
  1935.     if(sTagsExpr.IsEmpty())
  1936.     {
  1937.         exprTags.Set();
  1938.     }
  1939.     else
  1940.     {
  1941.         nRet = exprTags.Set(sTagsExpr, szCont);
  1942.         if(nRet)
  1943.             return nRet;
  1944.     }
  1945.  
  1946.     if(sFlagsExpr.IsEmpty())
  1947.     {
  1948.         exprFlags.Set();
  1949.     }
  1950.     else
  1951.     {
  1952.         nRet = exprFlags.Set(sFlagsExpr, szCont);
  1953.         if(nRet)
  1954.             return nRet;
  1955.     }
  1956.  
  1957.     // Output the index header.
  1958.  
  1959.     OutputFormatString(cur, pfmt, CFmtListIndex::tagPre, ptagParent);
  1960.     CTopic *pTopic;
  1961.     CTag   *ptagHead;
  1962.     
  1963.     // Walk the list of logs and output index entry for each file
  1964.     int i, nLogs;
  1965.     CCurTopicInfo curTopic(cur);
  1966.  
  1967.     for(i = 0, nLogs = cur.m_log.GetRealCount(); i < nLogs; i++)
  1968.     {
  1969.         curTopic.m_pTopic = pTopic = cur.m_log.GetTopicOutputOrder(i);
  1970.         ASSERT(pTopic);
  1971.  
  1972.         if(!pTopic->HasTags())
  1973.             continue;
  1974.  
  1975.         ptagHead = pTopic->GetTopicTag();
  1976.  
  1977.         if(UseTopicInIndex(ptagHead->sTag, 
  1978.                             cur.m_log.GetDocTag(pTopic->GetDocTag()),
  1979.                             pTopic->GetLocalDocTag(),
  1980.                             exprTags, exprFlags))
  1981.         {
  1982.             OutputFormatString(curTopic, pfmt, CFmtListIndex::tagFormat, 
  1983.                 cur.m_pTopic ? cur.m_pTopic->GetTopicTag() : NULL);
  1984.  
  1985.             cur.m_fileOutput.Write(szNewline, strlen(szNewline));
  1986.         }
  1987.     }
  1988.     
  1989.     OutputFormatString(cur, pfmt, CFmtListIndex::tagPost, ptagParent);
  1990.  
  1991.     return 0;
  1992. }
  1993.  
  1994.  
  1995.  
  1996.  
  1997. /*-----------------------------------------------------------------------
  1998. @func void | OutputWinHelpHPJFile | Generates a basic Help
  1999. project (HPJ) file for the Help Compiler. The name
  2000. of the HPJ file is contstructed from the base name of
  2001. the output RTF file with an "HPJ" extension. If a file
  2002. of this name already exists, the function returns without
  2003. creating a file.
  2004.  
  2005. @parm char * | szOutputFileName | Specifies a pointer to a
  2006. null-terminated string containg the name of the RTF
  2007. output file.
  2008.  
  2009. @parm PRUNOPTIONS | pOptions | Specifies a pointer to a
  2010. <t RUNOPTIONS> structure containing the runtime options.
  2011.  
  2012. @rdesc Void.
  2013. */
  2014. void OutputWinHelpHPJFile(
  2015.     const char * szOutputFileName,
  2016.     const char * szAutoduckName)
  2017. {
  2018.     CStdioFile file;
  2019.     CFileStatus status;
  2020.     CFileException e;
  2021.  
  2022.     char szFileName[_MAX_PATH];
  2023.     char szBaseName[_MAX_FNAME];
  2024.     char szDirName[_MAX_DIR];
  2025.     char szDriveName[_MAX_DRIVE];
  2026.     char szExtName[_MAX_EXT];
  2027.  
  2028.     _fullpath(szFileName, szOutputFileName, _MAX_PATH);
  2029.     _splitpath(szFileName, szDriveName, szDirName, 
  2030.         szBaseName, szExtName);
  2031.  
  2032.     // Use base name of output file
  2033.     sprintf(szFileName, "%s%s%s.HPJ", szDriveName, szDirName, szBaseName);
  2034.  
  2035.     // See if file already exists, don't overwrite if present.
  2036.     if(file.GetStatus(szFileName, status))
  2037.         return;
  2038.     
  2039.     if(!file.Open(szFileName, CFile::modeWrite | CFile::modeCreate | CFile::typeText, &e))
  2040.     {
  2041.         PrintError(szFileName, NO_LINE, e.m_cause + 100);
  2042.         return;
  2043.     }
  2044.     
  2045. TRY
  2046. {
  2047.     char szMsgBuf[1024];
  2048.     sprintf(szMsgBuf, "; HPJ file generated by %s\n\n", szAutoduckName);
  2049.     file.WriteString(szMsgBuf);
  2050.     file.WriteString("[options]\n\n");
  2051.     file.WriteString("[config]\n");
  2052.     file.WriteString(" BrowseButtons()\n\n");
  2053.     file.WriteString("[files]\n");
  2054.     sprintf(szMsgBuf, " %s%s\n\n", szBaseName, szExtName);
  2055.     file.WriteString(szMsgBuf);
  2056.     
  2057.     file.Close();
  2058. }
  2059. CATCH(CFileException, e)
  2060. {
  2061.     PrintError(szFileName, NO_LINE, e->m_cause + 100);
  2062. }
  2063. END_CATCH
  2064. }
  2065.  
  2066.  
  2067.